#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sqlite3.h>

#import <UIKit/UIImageView.h>
#import <UIKit/UIApplication.h>
#import <SpringBoard/SBApplicationController.h>
#import <SpringBoard/SBApplication.h>
#import <SpringBoard/SBSleepProofTimer.h>
#import <LockInfo/Plugin.h>
#import <AudioToolbox/AudioToolbox.h>

#import "FWCommon.h"
#import "FWTransToolbar.h"
#import "FWTableViewCell.h"
#import "FWToolbarViewCell.h"
#import "FWStatusbarViewCell.h"
#import "FWColorIconView.h"
#import "FWPlugin.h"
#import "FWListViewDelegate.h"
#import "FWListView.h"
#import "FWRoundedLabel.h"
#import "FWDotView.h"


static const NSInteger kToolbarHeight = 50;

NSInteger const kStatusLineIndex = -1;
NSInteger const kToolbarIndex = -2;


@interface LSStatusBarItem : NSObject

-(void)setImageName:(NSString*)name;
-(BOOL)isVisible;
-(void)setVisible:(BOOL)visible;

@end


@implementation FWPlugin

@synthesize items, notificationName, liPlugin, lastUpdate, fwApplication, fwDatasource, preferences, statusbarView, showStatusbar, timer, resourceName, nextScheduledActivity, debugMode, statusbarIcon;


// ******************************************************************************************
#pragma mark -
#pragma mark Initialisation and object management

- (id)initWithApplication:(FWApplication *)app
{
    return self;    
}

- (id)initWithApplication:(FWApplication*)app name:(NSString *)name
{
    if (!self.fwApplication) {
        toolbarRow = -1;
        toolbarOpenedDate = nil;
        showStatusbar = NO;
        
        self.fwApplication = app;
        
        self.resourceName = [NSString stringWithString:name];
        self.notificationName = [resourceName stringByAppendingString:@"Notification"];
        
        NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(repeatingNotification:) name:self.notificationName object:nil];
        
        XLog(@"LI:PICal: Registered listener for repeating events %@", self.notificationName);
    }        
    
    return self;
}

- (id)initWithPlugin:(LIPlugin*)pi
{
    FWApplication* app = [[[FWApplication alloc] init] autorelease];
    if ((self = [self initWithApplication:app])) {
        XLog(@"LI:%@: Initialising plugin *************", resourceName);

        mutexLock = [[NSObject alloc] init];

        self.liPlugin = pi;
        self.fwApplication.fwPlugin = self;
        self.preferences = [[[FWPreferences alloc] initWithPreferences:pi.preferences] autorelease];
        self.lastUpdate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
        
        pi.tableViewDataSource = self;
        pi.tableViewDelegate = self;
        
        XLog(@"LI:%@: Initialised: %@", resourceName, pi);
        DebugLog(@"LI:%@: bundleIdentifier: %@", resourceName, pi.bundleIdentifier);
        DebugLog(@"LI:%@: bundle: %@", resourceName, pi.bundle);
        DebugLog(@"LI:%@: globalBundle: %@", resourceName, pi.globalBundle);
        DebugLog(@"LI:%@: preferences: %@", resourceName, pi.preferences);
        DebugLog(@"LI:%@: globalPreferences: %@", resourceName, pi.globalPreferences);
        DebugLog(@"LI:%@: managedBundles: %@", resourceName, pi.managedBundles);
        
        NSNotificationCenter* center = [NSNotificationCenter defaultCenter];

        [center addObserver:self selector:@selector(timerNotification:) name:LITimerNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LITimerNotification);

        [center addObserver:self selector:@selector(viewReadyNotification:) name:LIViewReadyNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LIViewReadyNotification);

        [center addObserver:self selector:@selector(screenUndimmedNotification:) name:LIUndimScreenNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, LIUndimScreenNotification);
        
        // Notification when a window appears - check if the infoshade has appeared
        [center addObserver:self selector:@selector(windowVisibleNotification:) name:@"UIWindowDidBecomeVisibleNotification" object:nil];

        [center addObserver:self selector:@selector(timezoneChangeNotification:) name:NSSystemTimeZoneDidChangeNotification object:nil];
        DebugLog(@"LI:%@: Registered observer: %@", resourceName, NSSystemTimeZoneDidChangeNotification);
    }

    return self;
}

-(void)dealloc
{
    [items release];
    [liPlugin release];
    [fwApplication release];
    [fwDatasource release];
    [preferences release];
    [statusbarView release];
    [toolbarOpenedDate release];
    [resourceName release];
    [notificationName release];
    [nextScheduledActivity release];
    [mutexLock release];
    
    [super dealloc];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Libstatusbar support

- (void)showLibstatusBarIcon:(BOOL)show
{
    if (!self.statusbarIcon) {
        if ([self.fwDatasource respondsToSelector:@selector(libstatusbarIconName)]) {
            if (NSString* iconName = [self.fwDatasource libstatusbarIconName]) {
                self.statusbarIcon = [[[objc_getClass("LSStatusBarItem") alloc] initWithIdentifier:self.resourceName alignment:2] autorelease];
                XLog(@"LI:%@: Allocated libstatusbar icon %@", self.resourceName, self.statusbarIcon);
                
                [self.statusbarIcon setImageName:iconName];
            }
        }
    }

    if (self.statusbarIcon) {
//        XLog(@"LI:%@: Checking icon status", self.resourceName);
//        if ([self.statusbarIcon isVisible]) {
            XLog(@"LI:%@: Changing libstatusbar visibility = %d", self.resourceName, show);
            [self.statusbarIcon setVisible:show];
//        }
    }
}



// ******************************************************************************************
#pragma mark -
#pragma mark Attribute access

- (void)setBadgeValue:(NSString*)value hidden:(BOOL)hidden
{
    [statusbarView setBadgeValue:value hidden:hidden];
}
    

// ******************************************************************************************
#pragma mark -
#pragma mark Table view counters

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{
    NSInteger rows = self.items.count;
    
    if (self.showStatusbar) 
        ++rows;
    else
        self.statusbarView = nil;

    if (toolbarRow >= 0)
        ++rows;
    
    if (self.isDebugMode)
        ++rows;

    return rows;
}

- (NSInteger)tableView:(LITableView*)tableView numberOfItemsInSection:(NSInteger)section
{
    return self.items.count;
}

- (NSInteger)tableView:(LITableView*)tableView totalNumberOfItemsInSection:(NSInteger)section
{
    NSInteger rows;
    if ([fwDatasource respondsToSelector:@selector(tableView:totalNumberOfItems:preferences:)])
        rows = [fwDatasource tableView:tableView totalNumberOfItems:self.fwApplication preferences:self.preferences];
    else
        rows = self.items.count;

    return rows;
}

- (NSInteger)indexForRowAtIndexPath:(NSIndexPath *)indexPath withToolbar:(BOOL)toolbar
{
    NSInteger row = indexPath.row;
    if (self.showStatusbar) {
        if (row == 0)
            return kStatusLineIndex;
        else
            --row;
    }
    
    if (row == toolbarRow)
        return kToolbarIndex;
    
    if ((toolbarRow >= 0) && (row >= toolbarRow))
        --row;
    
    return row;    
}


// ******************************************************************************************
#pragma mark -
#pragma mark Table header support

-(NSString*) tableView:(LITableView*) tableView detailForHeaderInSection:(NSInteger)section
{
    return [fwDatasource tableView:tableView
          detailForHeaderInSection:section
                       application:self.fwApplication
                       preferences:self.preferences];
}

- (BOOL)respondsToSelector:(SEL)selector
{
    if (sel_isEqual(selector, @selector(tableView:totalNumberOfItemsInSection:))) {
        BOOL datasourceImplemented = [fwDatasource respondsToSelector:@selector(tableView:totalNumberOfItems:preferences:)];
        
        if (!datasourceImplemented)
            return NO;
        
        NSInteger totalItems = [fwDatasource tableView:nil totalNumberOfItems:self.fwApplication preferences:self.preferences];
        return (totalItems > 0) && (self.items.count != totalItems);
    }
    
    return [[self class] instancesRespondToSelector:selector];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Table contents support

static NSInteger const kMyTag = 57;

- (UITableViewCell*)tableView:(LITableView *)tableView viewCellForRowAtIndexPath:(NSIndexPath *)indexPath reuseId:(NSString*)reuseId
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        CGFloat height = [self tableView:tableView heightForRowAtIndexPath:indexPath];
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, height) reuseIdentifier:reuseId] autorelease];
        cell.backgroundColor = [UIColor clearColor];
        cell.contentMode = UIViewContentModeRedraw;
        cell.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        cell.autoresizesSubviews = YES;
    }
    
    return cell;
}

- (void)tableView:(LITableView *)tableView setViewForCell:(UITableViewCell*)cell view:(FWListView*)view
{
    view.frame = cell.frame;
    view.autoresizesSubviews = YES;
    
    view.tag = kMyTag;
    [cell.contentView addSubview:view];
}

- (FWListView*)tableView:(LITableView *)tableView viewForCell:(UITableViewCell*)cell
{
    return (FWListView*)[cell.contentView viewWithTag:kMyTag];
}

- (UITableViewCell *)tableView:(LITableView *)tableView toolbarCellForRowAtIndexPath:(NSIndexPath*)indexPath
{
    NSString* myIdentifier = [resourceName stringByAppendingString:@":TLBAR"];
    static NSInteger kToolbarTag = 100;
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:myIdentifier];
    if (cell == nil) {
        CGFloat height = kToolbarHeight;
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, height) reuseIdentifier:myIdentifier] autorelease];

        FWToolbarViewCell* tbv = [[FWToolbarViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, height)
                                                                    table:tableView
                                                             toolbarItems:[NSArray array]];
        
        NSBundle* lockinfo = [NSBundle bundleWithPath:@"/Library/LockInfo"];
#ifdef DEVELOPMENT_MODE
        lockinfo = [NSBundle bundleForClass:[self class]];
#endif
        
        UIImage* img = [UIImage imageWithContentsOfFile:[lockinfo pathForResource:@"LIOptionsBG" ofType:@"png"]];
        if (img)
            [tbv setBackgroundWithImage:img];
        else
            [tbv setBackgroundColor:[UIColor blackColor]];
        
        tbv.tag = kToolbarTag;
        [cell.contentView addSubview:tbv];
        
        [tbv release];
    }

    FWToolbarViewCell* toolbar = (FWToolbarViewCell*)[cell.contentView viewWithTag:kToolbarTag];
    
    NSArray* toolbarItems = [self toolbarItems];
    for (FWBarButtonItem* item in toolbarItems)
        item.tableView = tableView;

    BOOL updated = [self configureToolbarItems:toolbarItems];
    if (updated)
        [toolbar setItems:toolbarItems];

    return cell;
}

- (UITableViewCell *)tableView:(LITableView *)tableView statusbarCellForRowAtIndexPath:(NSIndexPath*)indexPath
{
    NSString* myIdentifier = [resourceName stringByAppendingString:@":STBAR"];
    static NSInteger kToolbarTag = 100;
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:myIdentifier];
    if (!cell) {
        CGFloat height = [FWStatusbarViewCell tableView:tableView heightForStatusbarWithLabelStyle:tableView.theme];
        
        cell = [[[FWTableViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, height) reuseIdentifier:myIdentifier] autorelease];
        cell.backgroundColor = [UIColor clearColor];
        cell.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        cell.contentMode = UIViewContentModeRedraw;
        cell.autoresizesSubviews = YES;
    }

    self.statusbarView = nil;    
    // If the cell has a statusbar and it's not the actual status bar then remove it. We'll add the real one later
    if (FWStatusbarViewCell* statusbar = (FWStatusbarViewCell*)[cell.contentView viewWithTag:kToolbarTag]) {
        if (statusbar != statusbarView)
            [statusbar removeFromSuperview];
    }

    // If we don't have a statusbar view thn create one and add it to the current view
    if (!statusbarView) {
        CGFloat height = [FWStatusbarViewCell tableView:tableView heightForStatusbarWithLabelStyle:tableView.theme];
        
        self.statusbarView = [[FWStatusbarViewCell alloc] initWithFrame:CGRectMake(0, 0, 320, height)
                                                                  table:tableView 
                                                           toolbarItems:[NSArray array]];
        statusbarView.tag = kToolbarTag;
        [cell.contentView addSubview:statusbarView];
        [statusbarView release];
    }
    else {
        [statusbarView removeFromSuperview];
        [cell.contentView addSubview:statusbarView];
    }
    
    NSArray* toolbarItems = [self statusbarItems];
    for (FWBarButtonItem* item in toolbarItems)
        item.tableView = tableView;
    
    BOOL updated = [self configureStatusbarItems:toolbarItems];
    if (updated)
        [statusbarView setItems:toolbarItems];
    
    NSString* caption = [self respondsToSelector:@selector(statusbarCaption)] ? [self statusbarCaption] : @"";
    NSString* badge = [self respondsToSelector:@selector(statusbarBadge)] ? [self statusbarBadge] : nil;
    NSString* icon = (caption.length > 0) && [self respondsToSelector:@selector(statusbarCaptionIcon)] ? [self statusbarCaptionIcon] : nil;
    NSInteger indent = [self statusbarIndent];
    
    // Update information
    [statusbarView setInformationForTable:tableView indent:indent label:caption];
    [statusbarView setBadgeValue:badge hidden:(badge == nil)];
    if (icon) {
        statusbarView.labelIcon2.icon = [[NSBundle bundleForClass:[self class]] pathForResource:icon ofType:@"png"];
        statusbarView.labelIcon2.hidden = NO;
    }
    else
        statusbarView.labelIcon2.hidden = YES;
    
    UIImage* subheaderImage = [[tableView sectionSubheader] stretchableImageWithLeftCapWidth:0 topCapHeight:0];
    [statusbarView setBackgroundWithImage:subheaderImage];
    [statusbarView setNeedsLayout];
    
    return cell;
}

- (UITableViewCell *)tableView:(LITableView *)tableView infoCellForRowAtIndexPath:(NSIndexPath*)indexPath forIndex:(NSInteger)row
{
    NSString *myIdentifier = [resourceName stringByAppendingString:@":LIST"];
    UITableViewCell *cell = [self tableView:tableView viewCellForRowAtIndexPath:indexPath reuseId:myIdentifier];
    FWListView* view = [self tableView:tableView viewForCell:cell];
    if (!view) {
        view = [fwDatasource application:fwApplication cellViewForTableView:tableView preferences:preferences];
        [self tableView:tableView setViewForCell:cell view:view];
    }
    
    NSDictionary* item = (NSDictionary*)[self.items objectAtIndex:row];
    [fwDatasource tableView:tableView setDetailsUsingView:view forItem:item application:fwApplication preferences:preferences];
    
    return cell;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Table contents support

- (NSDictionary*)itemForTableRow:(NSInteger)row
{
    row = [self indexForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0] withToolbar:NO];
    NSDictionary* item = (NSDictionary*)[self.items objectAtIndex:row];
    return item;
}

- (void)postUpdatedList:(NSArray*)list
{
    if (toolbarOpenedDate) {
        if ([[toolbarOpenedDate dateByAddingTimeInterval:5 * 60] isEqualOrEarlierThanDate:[NSDate date]])
            self.toolbarIndex = -1;
    }
    
    [self performSelectorOnMainThread:@selector(setItems:) withObject:list waitUntilDone:YES];
    self.lastUpdate = [NSDate date];
    
    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:1];
    [dict setObject:list forKey:@"items"];  
    [[NSNotificationCenter defaultCenter] postNotificationName:LIUpdateViewNotification object:self.liPlugin userInfo:dict];
}

- (void)updateList 
{
    @synchronized(mutexLock) {
        if (listLock)
            return;
        else
            listLock = YES;
    }
    
    DebugLog(@"LI:%@: Updating list...", resourceName);

    if (![fwApplication verifyApplication]) {
        XLog(@"LI:%@: Cannot find application!", resourceName);
        
        self.showStatusbar = NO;
        [self postUpdatedList:[NSArray array]];
    }
    else if (![fwDatasource application:self.fwApplication shouldUpdateForDate:self.lastUpdate] &&
             ![fwApplication shouldUpdateForDate:self.lastUpdate]) {
        XLog(@"LI:%@: Update is not required", resourceName);
    }
    else {
        DebugLog(@"LI:%@: plugin prefs = %@", resourceName, self.liPlugin.preferences);
        self.preferences = [[[FWPreferences alloc] initWithPreferences:self.liPlugin.preferences] autorelease];
        
        if ([self.fwApplication.applicationDelegate respondsToSelector:@selector(application:updateApplicationPrefs:pluginPrefs:)]) {
            NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:fwApplication.preferencesPath error:NULL];
            NSDate* updated = [attrs objectForKey:NSFileModificationDate];
            
            if ([updated timeIntervalSinceReferenceDate] > [fwApplication.preferencesUpdated timeIntervalSinceReferenceDate]) {
                fwApplication.preferencesUpdated = updated;
                NSDictionary* applicationPrefs = [NSDictionary dictionaryWithContentsOfFile:[fwApplication preferencesPath]];
                [self.fwApplication.applicationDelegate application:fwApplication
                                             updateApplicationPrefs:applicationPrefs 
                                                        pluginPrefs:self.preferences];
                
                DebugLog(@"LI:%@: Application prefs updated at %@ - read", resourceName, updated);
                DebugLog(@"LI:%@: Application prefs = %@", resourceName, applicationPrefs);
            }
            else
                DebugLog(@"LI:%@: Application preferences not updated since %@ - not read", resourceName, updated);
        }
        
        if ([self respondsToSelector:@selector(isStatusbarRequired)])
            self.showStatusbar = [self isStatusbarRequired];
        else
            self.showStatusbar = NO;
        
        // Update data and read from database
        NSArray *list = [fwDatasource application:fwApplication fetchNewDataForPrefs:preferences];
        [self postUpdatedList:list];
        
        if ([self.fwDatasource respondsToSelector:@selector(showLibstatusbarIconForItems:preferences:)])
            [self showLibstatusBarIcon:[fwDatasource showLibstatusbarIconForItems:list preferences:preferences]];
    }    
    
    @synchronized(mutexLock) {
        listLock = NO;
    }
}

- (NSInteger)lockinfoSectionForTableView:(UITableView *)tableView
{
    XLog(@"LI:%@: Searching for app section in lockinfo tableview %@", resourceName, tableView);
    
    NSObject* ctr = [objc_getClass("LockInfoController") sharedInstance];
    DebugLog(@"LI:%@: LockInfoController %@", resourceName, ctr);

    // Try to ask LI directly for the tableview section
    NSArray* visibles = nil;
    if ([ctr respondsToSelector:@selector(table)]) {
        NSObject* table = [ctr table];
        
        if ([table respondsToSelector:@selector(visibleSections)])
            visibles = [table visibleSections];
        DebugLog(@"LI:%@: Visible sections %@", resourceName, visibles);

        if ([table respondsToSelector:@selector(sectionForPlugin:inSection:)]) {
            NSObject* section = [table sectionForPlugin:self.liPlugin inSection:0];
            XLog(@"LI:%@: sectionForPlugin = %@", resourceName, section);
            if (section) {
                for (NSInteger index = 0; index < visibles.count; ++index) {
                    if ([visibles objectAtIndex:index] == section) {
                        XLog(@"LI:%@: Found matching index in LI, index=%i", resourceName, index);
                        return index;
                    }
                }
            }
        }
    }

    NSArray* sortOrder = nil;
    if (ctr && [ctr respondsToSelector:@selector(pluginSortOrder)])
        sortOrder = [ctr pluginSortOrder];
    DebugLog(@"LI:%@: Sort order %@", resourceName, sortOrder);

    // Try asking each visible section if it is our bundle
    if (sortOrder && visibles) {
        for (NSUInteger i = 0; i < visibles.count; ++i) {
            NSObject* visible = [visibles objectAtIndex:i];
            NSInteger index = 99999999;
            if ([visible respondsToSelector:@selector(index)])
                index = (NSInteger)[visible index];
            NSString* sortItem = (index < sortOrder.count) ? [sortOrder objectAtIndex:index] : @"<out-of-range>";
            if (sortItem) {
                DebugLog(@"LI:%@: Checking visible index %i plugin index %i = %@", resourceName, i, index, sortItem); 
                if ([sortItem isEqualToString:self.liPlugin.bundleIdentifier]) {
                    XLog(@"LI:%@: Found matching bundle %@ in LI, index=%i", resourceName, sortItem, i);
                    return i;
                }
            }
        }
    }

    NSInteger myRows = [self tableView:tableView numberOfRowsInSection:0];
    DebugLog(@"LI:%@: Can't find LI section, searching tableview; My row count = %i", resourceName, myRows);
    
    // Can't find my section without at least one row present
    if (myRows == 0)
        return -1;

    NSString* prefix = [NSString stringWithFormat:@"%@:", resourceName];
    for (NSInteger section = [tableView.dataSource numberOfSectionsInTableView:tableView] - 1; section >= 0; --section) {
        NSInteger rows = [tableView.dataSource tableView:tableView numberOfRowsInSection:section];
        if (rows == myRows) {
            UITableViewCell* cell = [tableView.dataSource tableView:tableView
                                              cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
            if ([cell.reuseIdentifier hasPrefix:prefix]) {
                XLog(@"LI:%@: Found section %i", resourceName, section);
                return section;
            }                
        }
    }
    
    return -1;
}

- (UITableViewCell*)tableView:(LITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger row = [self indexForRowAtIndexPath:indexPath withToolbar:YES];

    if (row == kStatusLineIndex)
        return [self tableView:tableView statusbarCellForRowAtIndexPath:indexPath];
    else if (row == kToolbarIndex)
        return [self tableView:tableView toolbarCellForRowAtIndexPath:indexPath];
    else if (self.isDebugMode && (row == self.items.count)) {
        NSString *myIdentifier = [resourceName stringByAppendingString:@":INFO"];
        UITableViewCell *cell = [self tableView:tableView viewCellForRowAtIndexPath:indexPath reuseId:myIdentifier];
        FWListView* view = [self tableView:tableView viewForCell:cell];
        if (!view) {
            view = [[[FWListView alloc] initWithFrame:CGRectZero table:tableView] autorelease];
            [self tableView:tableView setViewForCell:cell view:view];
        }

        view.detailText.style = tableView.theme.detailStyle;
        view.detailText.hidden = NO;
        [view.detailText setNeedsDisplay];
        view.summaryText.style = tableView.theme.detailStyle;
        view.summaryText.hidden = NO;
        [view.summaryText setNeedsDisplay];
        view.highlightText.hidden = YES;
        view.dot.hidden = YES;
        view.drawBorder = YES;
        
        [fwDatasource tableView:tableView setDetailsUsingView:view forItem:nil application:fwApplication preferences:preferences];
        [view setNeedsLayout];
        
        return cell;
    }
    else if (row >= self.items.count)
        return nil;
    else
        return [self tableView:tableView infoCellForRowAtIndexPath:indexPath forIndex:row];
}

- (CGFloat)tableView:(LITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger row = [self indexForRowAtIndexPath:indexPath withToolbar:YES];
    if (row == kStatusLineIndex)
        return [FWStatusbarViewCell tableView:tableView heightForStatusbarWithLabelStyle:tableView.theme];
    else if (row == kToolbarIndex)
        return kToolbarHeight;
    else if (self.isDebugMode && (row == self.items.count))
        return 2 * tableView.theme.summaryStyle.font.leading;
    else if (row < self.items.count)
        return [fwDatasource tableView:tableView 
                         heightForItem:[self.items objectAtIndex:row]
                           application:self.fwApplication
                           preferences:preferences];
    else
        return 0;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Data reload support

- (void)tableView:(LITableView*)table reloadDataInSection:(NSInteger)section
{
    DebugLog(@"LI:%@: reloadDataInSection called ############", resourceName);

    [self triggerUnconditionalRefresh];
}

- (void)triggerUnconditionalRefresh
{
    DebugLog(@"LI:%@: unconditionalRefresh triggered ############", resourceName);

    self.lastUpdate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
    [self updateList];
}

- (void)scheduleTriggerRedisplay:(NSArray*)params
{
    UITableView* tableView = [params objectAtIndex:0];

    XLog(@"LI:%@: Triggering redisplay", resourceName);
    
    NSInteger section = [self lockinfoSectionForTableView:tableView];
    section = -1;
    if (section >= 0) {
        [tableView beginUpdates];
        [tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
        [tableView endUpdates];
    }
    else
        [self triggerRedisplay];
}

- (void)triggerRedisplayForTableView:(UITableView*)tableView
{
    NSArray* params = [NSArray arrayWithObject:tableView];
    [self performSelectorOnMainThread:@selector(scheduleTriggerRedisplay:) withObject:params waitUntilDone:YES];
}

- (void)triggerRedisplay
{
    XLog(@"LI:%@: Triggering redisplay", resourceName);

    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:1];
    [dict setObject:items forKey:@"items"];  
    [[NSNotificationCenter defaultCenter] postNotificationName:LIUpdateViewNotification object:self.liPlugin userInfo:dict];
}

- (void)updateListForNotification
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    [self updateList];
    [pool release];
}

- (void)timerNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Timer notification - plugin disabled", resourceName);
        return;
    }

    XLog(@"LI:%@: Timer notification ############", resourceName);
    
    [self updateListForNotification];
}

- (void)timezoneChangeNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Timezone change notification - plugin disabled", resourceName);
        return;
    }
    
    DebugLog(@"LI:%@: Timezone change notification ############", resourceName);
    
    [NSTimeZone resetSystemTimeZone];
    [self triggerUnconditionalRefresh];
}

- (void)viewReadyNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: View ready notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: View ready notification ############", resourceName);
    
    [self updateListForNotification];
}

- (void)screenUndimmedNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Screen undimmed notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: Screen undimmed notification ############", resourceName);
    
    [self updateListForNotification];
}

static Class const liWindowShade = objc_getClass("LIShadeWindow");

- (void)windowVisibleNotification:(NSNotification*)notif
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Windowshade visible notification - plugin disabled", resourceName);
        return;
    }
    
    if (liWindowShade && [notif.object isMemberOfClass:liWindowShade]) {
        XLog(@"LI:%@: Windowshade visible notification ############", resourceName);
        
        [self updateListForNotification];
    }
}

- (void)timerTriggered:(NSTimer*)timer
{
    if (!self.liPlugin.enabled) {
        DebugLog(@"LI:%@: Scheduled notification - plugin disabled", resourceName);
        return;
    }
    
    XLog(@"LI:%@: Scheduled notification ############", resourceName);

    self.timer = nil;
    if (nextScheduledActivity) {
        if ([[NSDate date] isEqualOrLaterThanDate:nextScheduledActivity]) {  
            NSDate* date = [nextScheduledActivity retain];
            
            // We've reached the scheduled time
            [nextScheduledActivity release];
            nextScheduledActivity = nil;
            
            if ([self respondsToSelector:@selector(scheduledEventNotification:)])
                [self scheduledEventNotification:date];
            else
                [self updateListForNotification];

            [date release];
        }
        else
            [self scheduleNextTimerEvent:nextScheduledActivity];
    }
}


const NSInteger kTimeInterval1 = 30 * 60;
const NSInteger kTimeInterval2 = 15 * 60;
const NSInteger kTimeInterval3 = 10 * 60;
const NSInteger kTimeInterval4 = 5 * 60;

- (void)scheduleNextTimerEvent:(NSDate*)date
{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
    
    if (nextScheduledActivity)
        [nextScheduledActivity release];
    nextScheduledActivity = [date retain];
    
    // Try to handle the fact that timers may be late during deep sleep. Try to sneak up on
    // the time we want by setting timers closer to the date. The idea is that if one is late
    // then we will still be before the required time.
    NSTimeInterval remaining = [date timeIntervalSinceNow];
    if (remaining <= 0) {
        XLog(@"LI:%@: Scheduled time %@ is in the past - ignoring", resourceName, date);
        return;
    }
    else if (remaining >= kTimeInterval1)
        date = [date addTimeInterval:-kTimeInterval1];
    else if (remaining >= kTimeInterval2)
        date = [date addTimeInterval:-kTimeInterval2];
    else if (remaining >= kTimeInterval3)
        date = [date addTimeInterval:-kTimeInterval3];
    else if (remaining >= kTimeInterval4)
        date = [date addTimeInterval:-kTimeInterval4];

    XLog(@"LI:%@: Scheduling next wake event for %@ at %@", resourceName, nextScheduledActivity, date);

    [self performSelector:@selector(timerTriggered:)
               withObject:nil
               afterDelay:[date timeIntervalSinceNow]
                  inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Item preview

- (BOOL)tableViewIsToolbarVisible:(UITableView *)tableView
{
    return toolbarRow >= 0;
}

- (void)tableView:(UITableView *)tableView hideToolbarWithAnimation:(BOOL)animate
{
    if (toolbarRow >= 0)
        [self tableView:tableView setToolbarIndex:-1 withAnimation:animate];
}

- (void)setToolbarIndex:(NSInteger)index
{
    DebugLog(@"LI:%@: Setting toolbar to index %i", resourceName, index);
    
    if (index == toolbarRow)
        return;

    toolbarRow = index;
    
    [toolbarOpenedDate release];
    if (index < 0)
        toolbarOpenedDate = nil;
    else
        toolbarOpenedDate = [[NSDate date] retain];
}

- (NSInteger)toolbarIndex
{
    return toolbarRow;
}

- (void)tableView:(UITableView *)tableView setToolbarIndex:(NSInteger)index withAnimation:(BOOL)animate
{
    NSArray* params = [[NSArray alloc] initWithObjects:
                       tableView,
                       [NSNumber numberWithInt:index],
                       [NSNumber numberWithBool:animate],
                       nil];
    [self performSelectorOnMainThread:@selector(scheduleSetToolbarIndex:) withObject:params waitUntilDone:YES];
    [params release];
}

- (void)tableView:(UITableView *)tableView setToolbarIndex:(NSInteger)index withAnimation:(BOOL)animate updatingRow:(NSInteger)row
{
    NSArray* params = [[NSArray alloc] initWithObjects:
                       tableView,
                       [NSNumber numberWithInt:index],
                       [NSNumber numberWithBool:animate],
                       [NSNumber numberWithInt:row],
                       nil];

    [self performSelectorOnMainThread:@selector(scheduleSetToolbarIndex:) withObject:params waitUntilDone:YES];
    [params release];
}

- (void)scheduleSetToolbarIndex:(NSArray*)params
{
    UITableView* tableView = [params objectAtIndex:0];
    NSInteger index = [[params objectAtIndex:1] intValue];
    BOOL animate = [[params objectAtIndex:2] boolValue];
    NSInteger updateRow = params.count > 3 ? [[params objectAtIndex:3] intValue] : -1;
    
    XLog(@"LI:%@: Setting toolbar to index %i for tableView %@", resourceName, index, tableView);
    
    if (index == toolbarRow)
        return;

    NSObject* sectInfo = nil;
    NSInteger section = [self lockinfoSectionForTableView:tableView];

#ifndef DEVELOPMENT_MODE
    if (section >= 0) {
        NSObject* ctr = [objc_getClass("LockInfoController") sharedInstance];
        if (![ctr respondsToSelector:@selector(visibleSections)])
            section = -1;
    }
#endif
    section = -1;
    
    if (section >= 0) {
        DebugLog(@"LI:%@: Updating toolbar display using LockInfo display", resourceName);
        animate = YES;
        UITableViewRowAnimation animation = animate ? UITableViewRowAnimationFade : UITableViewRowAnimationNone;
        
        [tableView beginUpdates];
        
        if (toolbarRow >= 0) {
            NSInteger row = toolbarRow;
            if (self.showStatusbar)
                ++row;
            NSArray* deletes = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:row inSection:section]];
            
            DebugLog(@"LI:%@: Removing rows from table %@", resourceName, deletes);
            toolbarRow = -1;
            animation = animate && (index >= 0) ? UITableViewRowAnimationFade : animation;
            [tableView deleteRowsAtIndexPaths:deletes withRowAnimation:animation];
        }
        
        if (index >= 0) {
            NSInteger row = index;
            if (self.showStatusbar)
                ++row;
            NSArray* inserts = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:row inSection:section]];
            
            DebugLog(@"LI:%@: Adding rows to table %@", resourceName, inserts);
            toolbarRow = index;
            [tableView insertRowsAtIndexPaths:inserts withRowAnimation:animation];
        }

        // Allow updates if we have any
        if (updateRow >= 0) {
            NSArray* updates = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:updateRow inSection:section]];
            [tableView reloadRowsAtIndexPaths:updates withRowAnimation:UITableViewRowAnimationFade];
        }
        
#ifndef DEVELOPMENT_MODE
        [sectInfo setTotalCount:[self tableView:tableView totalNumberOfItemsInSection:section]];
        [sectInfo setItemCount:[self tableView:tableView numberOfItemsInSection:section]];
        [sectInfo setCount:[self tableView:tableView numberOfRowsInSection:section]];
#endif
        
        [tableView endUpdates];

        if (toolbarRow >= 0)
            [tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:toolbarRow + (showStatusbar ? 1 : 0)
                                                                 inSection:section]
                             atScrollPosition:UITableViewScrollPositionNone animated:YES];
    }
    else {
        DebugLog(@"LI:%@: Updating toolbar display using legacy refresh", resourceName);
        toolbarRow = index;
        [self triggerRedisplayForTableView:tableView];
    }
}

- (CGRect)previewCompatibleRect
{
    CGSize screenSize = FWGetCurrentScreenSize();
    return CGRectMake(0, 20, screenSize.width, screenSize.height - 20);
}

- (UIView*)tableView:(LITableView*)tableView previewWithFrame:(CGRect)rect forRowAtIndexPath:(NSIndexPath*)indexPath
{
    NSInteger row = [self indexForRowAtIndexPath:indexPath withToolbar:YES];
    if ((row == kStatusLineIndex) || (row == kToolbarIndex)) {
        DebugLog(@"LI:%@: System line selected for preview", resourceName);
        return nil;
    }
    
    BOOL allowPreviews = YES;
    
    if (row >= self.items.count || !allowPreviews)
        return nil;

    UIView* view = [fwDatasource tableView:tableView previewWithFrame:rect atIndex:row forItems:self.items application:fwApplication preferences:self.preferences];
    return view;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Notifications and alarms support

- (void)repeatingNotification:(NSNotification*)notif
{
    XLog(@"LI:%@: Repeating event notification ############", resourceName);

    if ([self respondsToSelector:@selector(repeatingEventNotification:)]) {
        XLog(@"LI:%@: Notification event", resourceName);
        [self repeatingEventNotification:notif];
    }
}

@end
